/* Created by Darren Otgaar on 2016/06/10. http://www.github.com/otgaard/zap */
#include "application.hpp"
#include <tools/log.hpp>
#include <GL/glew.h>
#include <GLFW/glfw3.h>

static void on_error_handler(int error, const char* description) {
    LOG_ERR("GLFW Error:", error, "Description:", description);
}

static void on_resize_handler(GLFWwindow* window_ptr, int width, int height) {
    if(auto app_ptr = reinterpret_cast<application*>(glfwGetWindowUserPointer(window_ptr))) {
        app_ptr->set_dims(width, height);
        app_ptr->on_resize(width, height);
    }
}

static void on_keypress_handler(GLFWwindow* window_ptr, int key, int scancode, int action, int mods) {
    if(auto app_ptr = reinterpret_cast<application*>(glfwGetWindowUserPointer(window_ptr))) {
        if(action == GLFW_PRESS)        app_ptr->on_keydown(key);
        else if(action == GLFW_RELEASE) app_ptr->on_keyup(key);
    }
}

static void on_mousemove_handler(GLFWwindow* window_ptr, double x, double y) {
    if(auto app_ptr = reinterpret_cast<application*>(glfwGetWindowUserPointer(window_ptr))) {
        app_ptr->on_mousemove(x, y);
    }
}

static void on_mousebutton_handler(GLFWwindow* window_ptr, int button, int action, int mods) {
    if(auto app_ptr = reinterpret_cast<application*>(glfwGetWindowUserPointer(window_ptr))) {
        if(action == GLFW_PRESS) app_ptr->on_mousedown(button);
        else                     app_ptr->on_mouseup(button);
    }
}

static void on_scroll_handler(GLFWwindow* window_ptr, double xinc, double yinc) {
    if(auto app_ptr = reinterpret_cast<application*>(glfwGetWindowUserPointer(window_ptr))) {
        app_ptr->on_mousewheel(xinc, yinc);
    }
}

application::application(const std::string& name, int width, int height) : sc_width_(width), sc_height_(height),
        app_name_(name) {
}

int application::run(const app_config& config) {
    glfwSetErrorCallback(::on_error_handler);
    if(!glfwInit()) return -1;

    glfwWindowHint(GLFW_SAMPLES, config.multisamples);
    glfwWindowHint(GLFW_DEPTH_BITS, config.depth_bits);
    glfwWindowHint(GLFW_STENCIL_BITS, config.stencil_bits);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, config.gl_major_version);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, config.gl_minor_version);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, config.gl_forward_compatibility ? GL_TRUE : GL_FALSE);
    glfwWindowHint(GLFW_OPENGL_PROFILE, config.gl_core_profile ? GLFW_OPENGL_CORE_PROFILE : GLFW_OPENGL_ANY_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, config.resizeable_window);

    window_ = glfwCreateWindow(sc_width_, sc_height_, app_name_.c_str(),
                               config.fullscreen ? glfwGetPrimaryMonitor() : nullptr, nullptr);
    if(!window_) {
        LOG_ERR("Error creating window - terminating");
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window_);

    glewExperimental = GL_TRUE;
    GLenum err = glewInit();
    if(err != GLEW_OK) {
        LOG("GLEW failed to initialise: ", glewGetErrorString(err));
        glfwTerminate();
        return -1;
    }
#ifdef LOGGING_ENABLED
    auto err_no = glGetError();
    if(err_no != GL_NO_ERROR) LOG("Suppressing error generated by GLEW", err_no);
#else
    glGetError();
#endif

    // Set the userdata pointer & callbacks
    glfwSetWindowUserPointer(window_, this);
    glfwSetWindowSizeCallback(window_, ::on_resize_handler);
    glfwSetKeyCallback(window_, ::on_keypress_handler);
    glfwSetCursorPosCallback(window_, ::on_mousemove_handler);
    glfwSetMouseButtonCallback(window_, ::on_mousebutton_handler);
    glfwSetScrollCallback(window_, ::on_scroll_handler);

    glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
    glfwSwapInterval(config.swap_interval);

    if(!initialise()) {
        LOG_ERR("Initialisation of this application failed.  Terminating.");
        return -1;
    }

    on_resize(sc_width_, sc_height_);

    timer_.start();
    double curr_time = timer_.getd();

    while(!glfwWindowShouldClose(window_)) {
        auto prev_time = curr_time;
        curr_time = timer_.getd();
        auto dt = float(curr_time - prev_time);
        update(curr_time, dt);

        draw();

        glfwSwapBuffers(window_);
        glfwPollEvents();
    }

    shutdown();

    glfwDestroyWindow(window_);
    glfwTerminate();
    return 0;
}

void application::on_keydown(int ch) {

}

void application::on_keyup(int ch) {

}

void application::on_resize(int width, int height) {
    sc_width_ = width; sc_height_ = height;
    set_viewport(0, 0, sc_width_, sc_height_);
}

void application::on_mousemove(double x, double y) {
    mouse_.x = int(x); mouse_.y = sc_height_ - int(y);
}

void application::on_mousedown(int button) {

}

void application::on_mouseup(int button) {

}

void application::on_mousewheel(double xoffset, double yoffset) {

}

void application::resize(int width, int height) {
    glfwSetWindowSize(window_, width, height);
}

// TODO: Should eventually sit in the renderer or camera, but here for now
void application::set_viewport(int x, int y, int width, int height) {
    glViewport(x, y, width, height);
}
